<?php
/**
 * 
 * @todo description of this class
 * 
 * @author Alton Crossley <crossleyframework@nogahidebootstrap.com>
 * @package Crossley Framework
 *  
 * @copyright Copyright (c) 2003-2009, Nogaide BootStrap INC. All rights reserved.
 * @license BSD http://opensource.org/licenses/bsd-license.php
 * @version $Id:$
 * 
 */
class X_Array_Tokenizer
{
    /**
    * Toggle cacheing (slows on high traffic sites)
    *
    * @var     Boolean
    * @access  public
    */
    public $bCache = true;
    /**
    * Debug switch for non production script testing
    *
    * @var     Boolean
    * @access  public
    */
    public $bDebug = false;
    /**
     * tell the engine not to output template comments even if it is in the
     * dev env.
     *
     * @var bool
     */
    public static $bOutputTemplateDelimiters = true;

    /**
     * Left side token delimiter
     *
     * @var unknown_type
     */
    private $sTokenL = '[::';
    /**
     * Right side token delimiter
     *
     * @var unknown_type
     */
    private $sTokenR = '::]';
    /**
     * Left side eval Token
     *
     * @var unknown_type
     */
    private $sEvL = '{$';
    private $sEvR = '}';
    private $cEsc = '\\';
    private $cEOL = '\n';
    
    /**
     * retrieve tokens from a template
     *
     * @param unknown_type $sTPLPath
     * @param array $aSkipTokens
     * @return unknown
     */
    public static function getTokens($sTPLPath)
    {
        $oEngine = new self();
        return $oEngine->_getTokens($sTPLPath, $aSkipTokens);
    }
    /**
     * Function to return all unique tokens in the provided template file.
     *
     * @param  string $sTPLPath - path to the template file
     * @prarm  array[optional] $aSkipTokens - tokens to ignore
     * @return array|false - returns false if error else array of string tokens
     */
    protected function _getTokens($sTPLPath)
    {
        if (!file_exists($sTPLPath)) return false;
        
        $aReturn = array();
        if (preg_match_all('/\[::([a-zA-Z0-9_]+)::\]*/', file_get_contents($sTPLPath), $aMatches))
        {
            $aReturn = array_unique($aMatches[1]);
        }
        return $aReturn;
    }



    /**
     * returns a categorized array of tokens from a string
     * @todo make this work (it just returns the tokens presently)
     *
     * @param string $sString
     * @return array
     */
    public static function getTokenCategoryArray($sString)
    {
        if (preg_match_all('/[::([\w\d_]+):([\w\d_]+)::]/i', $sString, $aMatches))
        {
            return array_unique($aMatches[1]);
        }

        return array();
    }

    /**
     * static template processing function for nested templates
     *
     * @param array $aDryEngine
     * @param string $sTemplate
     * @param string $bCache
     * @return string
     */
    public static function combine(Array $aDryEngine, $sTemplate, $bCache = true)
    {
        $oTemplateEngine = new self();
        $oTemplateEngine->bCache = $bCache;
        return $oTemplateEngine->process($aDryEngine, $sTemplate);
    }

    /**
     * replace tokens in a template with keyed values from an array
     * returns the combined string
     *
     * @param array $aDryEngine expected to be 2D for all parts other than main
     * @param string $sTemplate path to template file
     * @return string
     */
    public function process(Array $aDryEngine, $sTemplate)
    {
        $aTemplates = $this->parseTemplate($sTemplate);

        foreach ($aTemplates as $sName => $sString)
        {
            if ($sName !== 'main_template_body'
                && array_key_exists($sName, $aDryEngine)
                && is_array($aDryEngine[$sName]))
            {
                $aRepeat = array();
                foreach ($aDryEngine[$sName] as $aRepeatData)
                {
                    array_push($aRepeat, $this->_combineWithString($aRepeatData, $sString));
                }

                $aDryEngine[$sName] = implode("", $aRepeat);
            }
        }

        $sReturn = $this->_combineWithString($aDryEngine, $aTemplates['main_template_body']);

        if (array_key_exists('X_ENVIRONMENT', $_SERVER)
            && strtolower($_SERVER['X_ENVIRONMENT']) == 'dev'
            && self::$bOutputTemplateDelimiters)
        {
            if (realpath($sTemplate))
            {
                $sTemplate = realpath($sTemplate);
            }

            $sReturn = sprintf("\n<!-- DSENGINE:FILE:%1\$s:START -->\n%2\$s\n<!-- DSENGINE:FILE:%1\$s:END -->\n",
                        $sTemplate,
                        $sReturn);
        }

        return $sReturn;

    }

    /**
     * seperates out sub templates from a complex template
     *
     * @param stirng $sFileName
     * @return array
     */
    public function parseTemplate($sFileName)
    {
        $aReturn = array();
        $sStart = '<!--::repeat:%s:start::-->';
        $sEnd = '<!--::repeat:%s:end::-->';

        if (realpath($sFileName))
        {
            $sFileTime = filemtime($sFileName);

            $sNestName = $sFileName.'.nest.ds';
            if (realpath($sNestName)
                && (filemtime($sNestName) > $sFileTime)
                && $this->bCache)
            {
                $aReturn = unserialize(file_get_contents($sNestName));
            }
            else
            {
                $sFile = file_get_contents($sFileName);
//                print X_Debug::out('<strong>RAW FILE</strong>', "<pre>".htmlentities($sFile)."</pre><hr>");
                
                if (preg_match_all('/repeat:([\w_]+):start/i', $sFile, $aMatches))
                {
                    $aTemplateNames = $aMatches[1];
                    foreach ($aTemplateNames as $sName)
                    {
                        $sStartToken = sprintf($sStart, $sName);
                        $sEndToken = sprintf($sEnd, $sName);

                        $iStart = strpos($sFile, $sStartToken);
                        $iEnd = strpos($sFile, $sEndToken);
                        $iLength = ($iEnd-$iStart);

                        if (!$iStart || !$iEnd)
                        {
                            trigger_error('mismatched token for '.$sName);
                            continue;
                        }

                        $aReturn[$sName] = $this->prepareString(substr($sFile, $iStart+strlen($sStartToken), $iLength-strlen($sStartToken)));
                        $sFile = substr_replace($sFile, "[::{$sName}::]", $iStart, $iLength+strlen($sEndToken));
//                        print X_Debug::out('<strong>Parced</strong>', "<pre>".htmlentities($sFile)."</pre><hr>");
                    }
                }

                $aReturn['main_template_body'] = $this->prepareString($sFile);

                 if ($this->bCache) file_put_contents($sNestName, serialize($aReturn));
            }

        }
        else
        {
        	throw new Exception($sFileName . ' not found');
        }

        return $aReturn;
    }

    protected function _combineWithString($aDryEngine, $sDryTPL)
    {
        $oFilter = new X_Filter_Alnum(false, array('_'));
        if (is_array($aDryEngine))
	        foreach ($aDryEngine as $sKey => &$xValue)
	        {
	        	// normally keys arent filtered
	        	$v = $oFilter->filter($sKey);
	        	$$v = $xValue;
	        }

        set_error_handler('eHandler', E_NOTICE);
        eval("\$sDryTPL = \"$sDryTPL\";");
        restore_error_handler();

        return $this->finalizeString($sDryTPL);
    }

    /**
     * escape characters for eval
     * handles dollar sign properly
     *
     * @param string $sString
     * @return string
     */
    protected function prepareString($sString)
    {
        $sBuffer =  str_replace('$', '\\$', addslashes($sString));
        return str_replace($this->sTokenR, $this->sEvR, str_replace($this->sTokenL, $this->sEvL, $sBuffer));
    }
    /**
     * reverses escaping done by escapeString
     *
     * @param string $sString
     * @return string
     */
    protected function finalizeString($sString)
    {
        return str_replace('\\$', '$', stripslashes($sString));
    }
    
}

function eHandler($severity, $msg, $filename, $linenum)
{
	return;
}

?>